/**
 * $Id: ConnectorShape.java,v 1.22 2011-05-02 13:18:22 david Exp $
 * Copyright (c) 2010, Gaudenz Alder, David Benson
 */
package com.kg.rails.component.editor.shape;

import com.kg.rails.component.editor.canvas.Graphics2DCanvas;
import com.kg.rails.component.editor.util.Constants;
import com.kg.rails.component.editor.util.EditorLine;
import com.kg.rails.component.editor.util.EditorPoint;
import com.kg.rails.component.editor.util.EditorUtils;
import com.kg.rails.component.editor.view.CellState;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ConnectorShape extends BasicShape {

    /**
     *
     */
    public void paintShape(Graphics2DCanvas canvas, CellState state) {
        if (state.getAbsolutePointCount() > 1
                && configureGraphics(canvas, state, false)) {
            List<EditorPoint> pts = new ArrayList<EditorPoint>(
                    state.getAbsolutePoints());
            Map<String, Object> style = state.getStyle();

            // Paints the markers and updates the points
            // Switch off any dash pattern for markers
            boolean dashed = EditorUtils.isTrue(style, Constants.STYLE_DASHED);
            Object dashedValue = style.get(Constants.STYLE_DASHED);

            if (dashed) {
                style.remove(Constants.STYLE_DASHED);
                canvas.getGraphics().setStroke(canvas.createStroke(style));
            }

            translatePoint(pts, 0,
                    paintMarker(canvas, state, true));
            translatePoint(
                    pts,
                    pts.size() - 1,
                    paintMarker(canvas, state, false));

            if (dashed) {
                // Replace the dash pattern
                style.put(Constants.STYLE_DASHED, dashedValue);
                canvas.getGraphics().setStroke(canvas.createStroke(style));
            }

            paintPolyline(canvas, pts, state.getStyle());
        }
    }

    /**
     *
     */
    protected void paintPolyline(Graphics2DCanvas canvas,
                                 List<EditorPoint> points, Map<String, Object> style) {
        boolean rounded = isRounded(style)
                && canvas.getScale() > Constants.MIN_SCALE_FOR_ROUNDED_LINES;

        canvas.paintPolyline(points.toArray(new EditorPoint[points.size()]),
                rounded);
    }

    /**
     *
     */
    public boolean isRounded(Map<String, Object> style) {
        return EditorUtils.isTrue(style, Constants.STYLE_ROUNDED, false);
    }

    /**
     *
     */
    private void translatePoint(List<EditorPoint> points, int index, EditorPoint offset) {
        if (offset != null) {
            EditorPoint pt = (EditorPoint) points.get(index).clone();
            pt.setX(pt.getX() + offset.getX());
            pt.setY(pt.getY() + offset.getY());
            points.set(index, pt);
        }
    }

    /**
     * Draws the marker for the given edge.
     *
     * @return the offset of the marker from the end of the line
     */
    public EditorPoint paintMarker(Graphics2DCanvas canvas, CellState state, boolean source) {
        Map<String, Object> style = state.getStyle();
        float strokeWidth = (float) (EditorUtils.getFloat(style,
                Constants.STYLE_STROKEWIDTH, 1) * canvas.getScale());
        String type = EditorUtils.getString(style,
                (source) ? Constants.STYLE_STARTARROW
                        : Constants.STYLE_ENDARROW, "");
        float size = (EditorUtils.getFloat(style,
                (source) ? Constants.STYLE_STARTSIZE
                        : Constants.STYLE_ENDSIZE,
                Constants.DEFAULT_MARKERSIZE));
        Color color = EditorUtils.getColor(style, Constants.STYLE_STROKECOLOR);
        canvas.getGraphics().setColor(color);

        double absSize = size * canvas.getScale();

        List<EditorPoint> points = state.getAbsolutePoints();
        EditorLine markerVector = getMarkerVector(points, source, absSize);
        EditorPoint p0 = new EditorPoint(markerVector.getX(), markerVector.getY());
        EditorPoint pe = markerVector.getEndPoint();

        EditorPoint offset = null;

        // Computes the norm and the inverse norm
        double dx = pe.getX() - p0.getX();
        double dy = pe.getY() - p0.getY();

        double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
        double unitX = dx / dist;
        double unitY = dy / dist;
        double nx = unitX * absSize;
        double ny = unitY * absSize;

        // Allow for stroke width in the end point used and the
        // orthogonal vectors describing the direction of the
        // marker
        double strokeX = unitX * strokeWidth;
        double strokeY = unitY * strokeWidth;
        pe = (EditorPoint) pe.clone();
        pe.setX(pe.getX() - strokeX / 2.0);
        pe.setY(pe.getY() - strokeY / 2.0);

        IMarker marker = MarkerRegistry.getMarker(type);

        if (marker != null) {
            offset = marker.paintMarker(canvas, state, type, pe, nx, ny, absSize);

            if (offset != null) {
                offset.setX(offset.getX() - strokeX / 2.0);
                offset.setY(offset.getY() - strokeY / 2.0);
            }
        } else {
            // Offset for the strokewidth
            nx = dx * strokeWidth / dist;
            ny = dy * strokeWidth / dist;

            offset = new EditorPoint(-strokeX / 2.0, -strokeY / 2.0);
        }

        return offset;
    }

    /**
     * Hook to override creation of the vector that the marker is drawn along
     * since it may not be the same as the vector between any two control
     * points
     *
     * @param points     the guide points of the connector
     * @param source     whether the marker is at the source end
     * @param markerSize the scaled maximum length of the marker
     * @return a line describing the vector the marker should be drawn along
     */
    protected EditorLine getMarkerVector(List<EditorPoint> points, boolean source,
                                         double markerSize) {
        if (source) {
            return new EditorLine(points.get(1), points.get(0));
        } else {
            int pointCount = points.size();

            return new EditorLine(points.get(pointCount - 2),
                    points.get(pointCount - 1));
        }
    }

}
